package streaming;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;

import simulation.Simulator;

public class Neighbor {
	
	int id;
	double latency = -1;
	int sentCount = 0;
	int totalSentCount = 0;
	int outstanding; // Number of requests pending
	//int[] waitingFor; // Array of seqno of packets being waited for - when these expire, outstanding decreases
	
	HashMap<Integer, WaitingFor> waitingFor;
	
	HashMap notifiedPackets = new HashMap();
	Integer[] keys = new Integer[0];
	
	// These variables are not really important - for debugging
	int goodNotificationsCount = 0;
	//
	
	public Neighbor(int newId) {
		id = newId;
		waitingFor = new HashMap<Integer, WaitingFor>();
		
		
//		waitingFor = new int[StreamingConfig.MCASTMAXOUTSTANDING];
//		for (int i = 0; i < StreamingConfig.MCASTMAXOUTSTANDING; i++)
//			waitingFor[i] = -1;
	}
	
	public void addNotifiedPacket(int cid) {
		int binIndex = cid/StreamingConfig.MCASTRATE;
		boolean[] bin = (boolean[])notifiedPackets.get(new Integer(binIndex));
		if (bin == null) {
			bin = new boolean[StreamingConfig.MCASTRATE];
			Arrays.fill(bin, false);
			notifiedPackets.put(new Integer(binIndex), bin);
			keys = (Integer[])notifiedPackets.keySet().toArray(new Integer[notifiedPackets.size()]);
		}
		int offset = cid - binIndex*StreamingConfig.MCASTRATE;
		bin[offset] = true;
	}
	
	public void removeNotifiedPacket(int cid) {
		int binIndex = cid/StreamingConfig.MCASTRATE;
		boolean[] bin = (boolean[])notifiedPackets.get(new Integer(binIndex));
		if (bin == null)
			return;
		int offset = cid - binIndex*StreamingConfig.MCASTRATE;
		if (bin[offset])
			bin[offset] = false;
			// Remove bin entry if there are no other notified packets in it
			for (int i = 0; i < StreamingConfig.MCASTRATE; i++)
				if (bin[i])
					return;
			notifiedPackets.remove(new Integer(binIndex));
			keys = (Integer[])notifiedPackets.keySet().toArray(new Integer[notifiedPackets.size()]);
	}
	
	public void addRequestedPacket(int cid, double timeRequested) {
		if (waitingFor.get(cid) == null){
			WaitingFor wf = new WaitingFor(cid, timeRequested);
			this.waitingFor.put(cid, wf);
			outstanding++;
		}
		else {
			System.out.println(id + "this is messed up - neighbor file");
			System.out.println(id + " problems adding Requested Packet (in Neighbor.java)");
		}
		
		
//			if (waitingFor[i] == cid)
//				System.out.println(id + "this is messed up - neighbor file");
//			if (waitingFor[i] == -1) {
//				waitingFor[i] = cid;
//				outstanding++;
//				return;
//			}
//		}
//		System.out.println(id + " problems adding Requested Packet (in Neighbor.java)");
	}
	
	/**
	 * Remove a packet that was requested by a host
	 * @param cid The sequence number of the chunk to be removed from controller list
	 * @return true if removed was successful, false if not
	 */
	public boolean removeRequestedPacket(int cid) {
		WaitingFor wf = waitingFor.get(cid);
		
		if (wf != null){
			waitingFor.remove(cid); //removes waiting for information for that chunk
			outstanding--;
			return true;
		}
		
		return false;
		
//		for (int i = 0; i < StreamingConfig.MCASTMAXOUTSTANDING; i++)
//			if (waitingFor[i] == cid) {
//				waitingFor[i] = -1;
//				outstanding--;	
//				return true;
//			}
//		return false;
	}
	
	/**
	 * Removes all notifications smaller than given argument.
	 * @param initialSeqno
	 */
	public void update(int initialCid) {
		int binIndex = initialCid/StreamingConfig.MCASTRATE;
		for (int i = 0; i < keys.length; i++) {
			int index = keys[i].intValue();
			if (index < binIndex)
				notifiedPackets.remove(new Integer(index));
		}
		keys = (Integer[])notifiedPackets.keySet().toArray(new Integer[notifiedPackets.size()]);
		sentCount = 0;
	}
	
	/** 
	 * @return a random packet to pick from notifications received
	 */
	public int getRandomNotification() {
		Random rnd = Simulator.getInstance().random;
		if (notifiedPackets.isEmpty()) {
			return -1;
		}
		Integer binIndex = keys[rnd.nextInt(keys.length)];
		boolean[] map = (boolean[])notifiedPackets.get(binIndex);
		int offset = rnd.nextInt(StreamingConfig.MCASTRATE);
		int value = -1;
		int start = binIndex.intValue()*StreamingConfig.MCASTRATE;
		//System.out.println("size " + map.length + " offset " + offset);
		while (!map[offset]) {
			offset = (offset < (StreamingConfig.MCASTRATE - 1)) ? offset + 1 : 0;
		}
		value = start + offset;
		return value;
	}
	
	public int getValue(Integer binIndex){
		Random rnd = Simulator.getInstance().random;

		boolean[] map = (boolean[])notifiedPackets.get(binIndex);
		int offset = rnd.nextInt(StreamingConfig.MCASTRATE);
		int value = -1;
		int start = binIndex.intValue()*StreamingConfig.MCASTRATE;
		//System.out.println("size " + map.length + " offset " + offset);
		while (!map[offset]) {
			offset = (offset < (StreamingConfig.MCASTRATE - 1)) ? offset + 1 : 0;
		}
		value = start + offset;
		return value;
	}
	
	/** 
	 * @return a random old packet to pick from notifications received - used for malicious 
	 * behavior.
	 */
	public int getOldNotification(int maxCid) {
		Random rnd = Simulator.getInstance().random;
		if (notifiedPackets.isEmpty()) {
			return -1;
		}
		ArrayList oldKeys = new ArrayList();
		// Filter out newer packets - just want to request old ones
		int max = (int)(maxCid/StreamingConfig.MCASTRATE);
		for (int i = 0; i < keys.length; i++) {
			if (keys[i].intValue() <= max) {
				oldKeys.add(keys[i]);
				//System.out.println("value = " + keys[i].intValue() + " Max = " + max);
			}
		}
		if (oldKeys.size() == 0) {
			return -1;
		}
		Integer binIndex = (Integer)oldKeys.get(rnd.nextInt(oldKeys.size()));
		boolean[] map = (boolean[])notifiedPackets.get(binIndex);
		int offset = rnd.nextInt(StreamingConfig.MCASTRATE);
		int value = -1;
		int start = binIndex.intValue()*StreamingConfig.MCASTRATE;
		//System.out.println("size " + map.length + " offset " + offset);
		while (!map[offset]) {
			offset = (offset < (StreamingConfig.MCASTRATE - 1)) ? offset + 1 : 0;
		}
		value = start + offset;
		return value;
	}
	
	/**
	 * @param cid
	 * @return if packet cid has been notified by this neighbor
	 */
	public boolean hasNotified(int cid) {
		int binIndex = cid/StreamingConfig.MCASTRATE;
		boolean[] bin = (boolean[])notifiedPackets.get(new Integer(binIndex));
		if (bin == null)
			return false;
		int offset = cid - binIndex*StreamingConfig.MCASTRATE;
		return bin[offset];
	}

	public void printMap(int cid) {
		int binIndex = cid/StreamingConfig.MCASTRATE;
		boolean[] bin = (boolean[])notifiedPackets.get(new Integer(binIndex));
		if (bin == null)
			System.out.println("no map");
		else {
			for (int i = 0; i < StreamingConfig.MCASTRATE; i++)
				System.out.print(bin[i] + " ");
			System.out.println();
		}
	}
}